﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Web;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;

using AmazonBookLinker.Properties;
using AmazonBookLinker.Diagnostics;
using WindowsLive.Writer.Api;

namespace AmazonBookLinker
{
  public partial class MainSearchForm
  {
    #region Enums

    private enum ClearKeywords
    {
      Yes,
      No
    }

    #endregion

    #region Constants

    private const string DefaultXmlNamespaceAttribute = "this";
    private const int MaxReturnedItemsPerPageFromAmazon = 10;
    private const string DefaultUnavailableImageUrl = @"http://g-ecx.images-amazon.com/images/G/01/x-site/icons/no-img-sm._V47056216_.gif";

    #endregion

    #region Constructors

    public MainSearchForm()
    {
      InitializeComponent();
      SetFormProperties();
      PopulateImageSizesList();
    }


    private void SetFormProperties()
    {
      Text = string.Format( "Amazon Book Linker v{0}.{1} (powered by Amazon's Product Advertising API)", Assembly.GetExecutingAssembly().GetName().Version.Major, Assembly.GetExecutingAssembly().GetName().Version.Minor );
    }

    #endregion

    #region Event Handlers

    private void btnLoadTemplate_Click(object sender, EventArgs e)
    {
      DialogResult dialogResult = this.loadTemplateDialog.ShowDialog();

      if (dialogResult == DialogResult.OK)
      {
        Context.TemplateFileName = loadTemplateDialog.FileName;
        Context.TemplateContent = File.ReadAllText( loadTemplateDialog.FileName );
        UpdateHtmlPreview();
      }
    }

    private void btnViewSource_Click( object sender, EventArgs e )
    {
      using ( HtmlCodeViewerForm viewer = new HtmlCodeViewerForm() )
      {
        viewer.Text = "View Template HTML";
        viewer.DefaultButton.Text = "&OK";
        viewer.Tag = "View";
        viewer.HtmlText = webPreview.DocumentText;
        viewer.Wrap = true;
        viewer.ReadOnly = true;
        viewer.EnableMacrosQuickInsertContextMenu = false;
        viewer.ShowDialog();
      }
    }


    private void btnModifyTemplate_Click( object sender, EventArgs e )
    {
      using ( HtmlCodeViewerForm viewer = new HtmlCodeViewerForm() )
      {
        viewer.Text = "Modify Template HTML";
        viewer.DefaultButton.Text = "&Save";
        viewer.Tag = "Modify";
        viewer.HtmlText = PluginHelper.HtmlTemplateContent;
        viewer.Wrap = true;
        viewer.ReadOnly = false;
        viewer.EnableMacrosQuickInsertContextMenu = true;
        DialogResult result = viewer.ShowDialog();

        if ( result == DialogResult.OK )
          UpdateHtmlPreview();
      }
    }


    private void lstResults_SelectedIndexChanged( object sender, EventArgs e )
    {
      UpdateHtmlPreview();
      AcceptButton = btnInsert;
    }


    private void lstResults_ItemSelectionChanged( object sender, ListViewItemSelectionChangedEventArgs e )
    {
      btnInsert.Enabled = btnDetails.Enabled = e.ItemIndex > -1;
    }


    private void lstResults_MouseDoubleClick( object sender, MouseEventArgs e )
    {
      DisplayBookDetails();
    }


    private void btnInsert_Click( object sender, EventArgs e )
    {
      DialogResult = DialogResult.OK;
    }


    private void btnAdvanced_Click( object sender, EventArgs e )
    {
      using ( SettingsForm form = new SettingsForm() )
      {
        form.ShowDialog();
      }
    }


    private void btnSearch_Click( object sender, EventArgs e )
    {
      // Clear current previous results
      ClearResults( ClearKeywords.No );

      // Disable the Search button
      btnSearch.Enabled = false;

      // Display the WaitCursor
      Cursor = Cursors.WaitCursor;
      List<Book> books = null;

      try
      {
        // Perform search
        books = DoSearch();
      }
      catch ( Exception ex )
      {
        PluginTracer.LogException( ex, "Performing the search operation", new Dictionary<string, string>{{"search value", Keywords}} );
        PluginTracer.DisplayUnexpectedException( ex );
        return;
      }
      finally
      {
        // Display the Default cursor
        Cursor = Cursors.Default;

        // Enable the Search button
        btnSearch.Enabled = true;
      }

      if ( books.Count == 0 )
      {
        MessageBox.Show( "No results found.", "Search Results", MessageBoxButtons.OK, MessageBoxIcon.Information );
        return;
      }

      DisplayBooks( books );

      lblResults.Text = string.Format( "Showing results {0} of {1}", books.Count, Settings.Default.MaxResults );
    }


    private void txtKeywords_TextChanged( object sender, EventArgs e )
    {
      btnSearch.Enabled = (!string.IsNullOrEmpty( txtKeywords.Text.Trim() ));
    }


    private void cboImageSizes_SelectedIndexChanged( object sender, EventArgs e )
    {
      if ( SelectedBook == null )
        return;

      UpdateHtmlPreview();
    }


    private void webPreview_Navigating( object sender, WebBrowserNavigatingEventArgs e )
    {
      e.Cancel = e.Url.AbsoluteUri != "about:blank";
    }


    private void picAwsLogo_MouseClick( object sender, MouseEventArgs e )
    {
      Process.Start( "http://aws.amazon.com/associates/" );
    }


    private void btnAbout_Click( object sender, EventArgs e )
    {
      using ( AboutBoxForm form = new AboutBoxForm() )
      {
        form.ShowDialog();
      }
    }


    private void btnDetails_Click( object sender, EventArgs e )
    {
      DisplayBookDetails();
    }


    private void MainSearchForm_Load( object sender, EventArgs e )
    {
      Context.CheckForNewVersion(true);
      txtKeywords.Select();
    }

    private void txtKeywords_Enter( object sender, EventArgs e )
    {
      AcceptButton = btnSearch;
    }

    #endregion

    #region Methods

    private void PopulateImageSizesList()
    {
      cboImageSizes.Items.Add( BookImageSize.Small );
      cboImageSizes.Items.Add( BookImageSize.Medium );
      cboImageSizes.Items.Add( BookImageSize.Large );

      for ( int i = 0; i < cboImageSizes.Items.Count; i++ )
      {
        if (cboImageSizes.Items[i].ToString() == Settings.Default.DefaultImageSize)
        {
          cboImageSizes.SelectedIndex = i;
          break;
        }
      }
    }


    private void DisplayBooks( IEnumerable<Book> books )
    {
      foreach ( Book book in books )
        AddBookToListView( book );

      // Set the focus on the ListView so that the user can scroll up/down on it with the keyboard
      lstResults.Focus();
    }


    private void AddBookToListView( Book book )
    {
      ListViewItem bookItemOwner = new ListViewItem( book.Title );

      bookItemOwner.Tag = book;

      bookItemOwner.SubItems.Add( new ListViewItem.ListViewSubItem( bookItemOwner, book.Author ) );

      lstResults.Items.Add( bookItemOwner );

      Application.DoEvents();
    }


    private void DisplayBookDetails()
    {
      using ( BookInfoForm form = new BookInfoForm( SelectedBook ) )
      {
        form.ShowDialog();
      }
    }


    private void UpdateHtmlPreview()
    {
      if ( SelectedBook == null )
        return;

      webPreview.DocumentText = PluginHelper.HtmlTemplateContent
          .Replace( Tokens.BookDetailsUrl, SelectedBook.DetailsUrl.Replace( "%3F", "?" ).Replace( "%3D", "=" ).Replace( "%26", "&" ) )
          .Replace( Tokens.BookTitle, SelectedBook.Title )
          .Replace( Tokens.BookDescription, SelectedBook.Description)
          .Replace( Tokens.BookPrice, SelectedBook.Price )
          .Replace( Tokens.BookAuthor, SelectedBook.Author )
          .Replace( Tokens.BookAsin, SelectedBook.ASIN )
          .Replace( Tokens.BookPublisher, SelectedBook.Publisher )
          .Replace( Tokens.BookPublicationDate, SelectedBook.PublicationDate )
          .Replace( Tokens.BookNumberOfPages, SelectedBook.NumberOfPages )
          .Replace( Tokens.BookIsbn10, SelectedBook.ISBN10 )
          .Replace( Tokens.BookIsbn13, SelectedBook.ISBN13 )
          .Replace(Tokens.BookRating, SelectedBook.Rating.ToString()  )
          .Replace( Tokens.BookImageUrl, GetSelectedBookImageUrl() );
    }


    private string GetSelectedBookImageUrl()
    {
      switch ( (BookImageSize)Enum.Parse( typeof(BookImageSize), cboImageSizes.SelectedItem.ToString() ) )
      {
        case BookImageSize.Small:
          return SelectedBook.SmallImageUrl;
        case BookImageSize.Medium:
          return SelectedBook.MediumImageUrl;
        case BookImageSize.Large:
          return SelectedBook.LargeImageUrl;
      }

      return SelectedBook.MediumImageUrl;
    }


    private static IEnumerable<Book> GetBooks( XPathNodeIterator iterator )
    {
      List<Book> books = new List<Book>();

      // At this position, iterator is ready to iterate over a collection of Item elements
      while ( iterator.MoveNext() )
      {
        XPathNavigator currentNode = iterator.Current;
        books.Add( CreateBook( currentNode ) );
      }

      return books;
    }


    private static Book CreateBook( XPathNavigator node )
    {
      Book book = new Book
                    {
                        ASIN = GetBookASIN( node ),
                        Author = GetBookAuthor( node ),
                        DetailsUrl = GetBookDetailsPageUrl(node),
                        Image = GetBookImage(BookImageSize.Medium, node),
                        Price = GetBookPrice(node),
                        Rating = GetBookRating(node),
                        PublicationDate = GetBookPublicationDate(node),
                        Publisher = GetBookPublisher(node),
                        NumberOfPages=GetBookNumberOfPages(node),
                        ISBN10 = GetBookISBN10(node),
                        ISBN13 = GetBookISBN13(node),
                        Title = GetBookTitle(node),
                        Description = GetBookDescription(node),
                        SmallImageUrl = GetBookImageUrl( BookImageSize.Small, node ),
                        MediumImageUrl = GetBookImageUrl( BookImageSize.Medium, node ),
                        LargeImageUrl = GetBookImageUrl( BookImageSize.Large, node )
                    };

      return book;
    }

    private void ClearResults( ClearKeywords clearKeywords )
    {
      if ( clearKeywords == ClearKeywords.Yes )
        txtKeywords.Clear();

      txtKeywords.Focus();
      btnInsert.Enabled = false;
      lstResults.Items.Clear();
      lblResults.Text = string.Empty;
      webPreview.DocumentText = string.Empty;
      Application.DoEvents();
    }


    private static XPathNodeIterator CreateItemCollectionNodeIterator( XmlDocument document )
    {
      XPathNavigator navigator = document.CreateNavigator();
      return navigator.Select( string.Format( @"/{0}:ItemSearchResponse/{0}:Items/{0}:Item", DefaultXmlNamespaceAttribute ), GetXmlNamespaceManager( document, DefaultXmlNamespaceAttribute ) );
    }


    private static string GetBookASIN( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:ASIN", DefaultXmlNamespaceAttribute ), navigator );

      return node == null ? string.Empty : node.Value;
    }


    private static string GetBookPrice( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:Offers/{0}:Offer/{0}:OfferListing/{0}:Price/{0}:FormattedPrice", DefaultXmlNamespaceAttribute ), navigator );

      return node == null ? string.Empty : node.Value;
    }

    private static double GetBookRating(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:CustomerReviews/{0}:AverageRating", DefaultXmlNamespaceAttribute), navigator);

      double rating;
      return node == null ? double.NaN : double.TryParse(node.Value, out rating) ? rating : double.NaN;
    }


    private static string GetBookDetailsPageUrl( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:DetailPageURL", DefaultXmlNamespaceAttribute ), navigator );

      return node == null ? string.Empty : node.Value;
    }


    private static string GetBookAuthor( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:ItemAttributes/{0}:Author", DefaultXmlNamespaceAttribute ), navigator );

      return node == null ? string.Empty : node.Value.Replace( "~", " and " );
    }

    private static string GetBookISBN10(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:ItemAttributes/{0}:ISBN", DefaultXmlNamespaceAttribute), navigator);

      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookISBN13(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:ItemAttributes/{0}:EAN", DefaultXmlNamespaceAttribute), navigator);

      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookNumberOfPages(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:ItemAttributes/{0}:NumberOfPages", DefaultXmlNamespaceAttribute), navigator);

      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookPublisher(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:ItemAttributes/{0}:Publisher", DefaultXmlNamespaceAttribute), navigator);

      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookPublicationDate(XPathNavigator navigator)
    {
      XPathNavigator node = GetSingleNodeFromItemElement(string.Format(@"{0}:ItemAttributes/{0}:PublicationDate", DefaultXmlNamespaceAttribute), navigator);

      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookTitle( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:ItemAttributes/{0}:Title", DefaultXmlNamespaceAttribute ), navigator );
      return node == null ? string.Empty : node.Value;
    }

    private static string GetBookDescription( XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:EditorialReviews/{0}:EditorialReview/{0}:Content", DefaultXmlNamespaceAttribute ), navigator );
      
      return node == null ? string.Empty : PluginHelper.RemoveHtmlTags( node.Value );
    }


    private static Image GetBookImage( BookImageSize size, XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:{1}/{0}:URL", DefaultXmlNamespaceAttribute, GetDescription( size ) ), navigator );

      if ( node == null )
        return PluginHelper.GetEmbeddedImage( "unavailable.gif" );

      PluginHttpRequest request = new PluginHttpRequest( node.Value, HttpRequestCacheLevel.CacheIfAvailable );

      using ( Stream responseStream = request.GetResponse() )
      {
        return Image.FromStream( responseStream );
      }
    }


    private static string GetBookImageUrl( BookImageSize size, XPathNavigator navigator )
    {
      XPathNavigator node = GetSingleNodeFromItemElement( string.Format( @"{0}:{1}/{0}:URL", DefaultXmlNamespaceAttribute, GetDescription( size ) ), navigator );

      return node == null ? DefaultUnavailableImageUrl : node.Value;
    }


    private static string GetDescription( Enum value )
    {
      FieldInfo fieldInfo = value.GetType().GetField( value.ToString() );
      DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes( typeof(DescriptionAttribute), false );
      return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
    }


    private static XPathNavigator GetSingleNodeFromItemElement( string xpath, XPathNavigator navigator )
    {
      XmlDocument document = new XmlDocument();
      document.LoadXml( navigator.OuterXml );
      return navigator.SelectSingleNode( xpath, GetXmlNamespaceManager( document, xpath.Substring( 0, xpath.IndexOf( ":" ) ) ) );
    }


    private static XmlNamespaceManager GetXmlNamespaceManager( XmlDocument document, string defaultPrefixIfEmpty )
    {
      XmlNamespaceManager manager = new XmlNamespaceManager( document.NameTable );

      XmlNode singleNode = document.SelectSingleNode( "/*" );

      foreach ( XmlAttribute attribute in singleNode.Attributes )
        manager.AddNamespace( attribute.Prefix == string.Empty ? defaultPrefixIfEmpty : attribute.LocalName, attribute.Value );

      return manager;
    }


    public string GetHtmlLinkForSelectedBook()
    {
      return webPreview.DocumentText;
    }


    private List<Book> DoSearch()
    {
      List<Book> books = new List<Book>( Settings.Default.MaxResults );
      AmazonService service = new AmazonService( Settings.Default.AssociateTag,
                                                 ServiceLocale );

      for ( int itemPage = 1; itemPage <= Settings.Default.MaxResults / MaxReturnedItemsPerPageFromAmazon; itemPage++ )
      {
        XmlDocument searchResults = service.Search( Keywords, itemPage );

        XPathNodeIterator itemIterator = CreateItemCollectionNodeIterator( searchResults );

        // There are no results if the iterator is empty on the first page
        if ( itemPage == 1 &&
             itemIterator.Count == 0 )
        {
          // At this point, the books list is empty, so we can simply return it
          return books;
        }

        books.AddRange( GetBooks( itemIterator ) );

        if ( itemIterator.Count < MaxReturnedItemsPerPageFromAmazon )
          break;
      }

      return books;
    }

    #endregion

    #region Properties

    private Book SelectedBook
    {
      get
      {
        ListViewItem selectedItem = lstResults.FocusedItem;
        if ( selectedItem == null )
          return null;

        return selectedItem.Tag as Book;
      }
    }

    private static AmazonLocale ServiceLocale
    {
      get
      {
        switch ( Settings.Default.AmazonLocale )
        {
          case "Amazon.com":
            return AmazonLocale.US;
          case "Amazon.ca":
            return AmazonLocale.CA;
          case "Amazon.co.uk":
            return AmazonLocale.UK;
          case "Amazon.de":
            return AmazonLocale.DE;
          case "Amazon.fr":
            return AmazonLocale.FR;
          case "Amazon.jp":
            return AmazonLocale.JP;
        }

        return AmazonLocale.US;
      }
    }

    private string Keywords
    {
      get { return txtKeywords.Text.Trim(); }
    }

    #endregion
  }
}